# Module 'tablewin'

# Affiche un tableau dont chaque case est une action :

#	   A1   |   A2   |   A3   |  ....  |   AN
#	   B1   |   B2   |   B3   |  ....  |   BN
#	   C1   |   C2   |   C3   |  ....  |   CN
#	   ..   |   ..   |   ..   |  ....  |   ..
#	   Z1   |   Z2   |   Z3   |  ....  |   ZN

# Toutes les colonnes ne doivent pas nécessairement avoir
# la même longueur.
# La structure de données utilisée en interne pour représenter
# le tableau est une liste de colonnes; chaque colonne constitue
# une liste d'éléments. Chaque élément constitue une paire
# formée d'une chaîne et d'une action. Le premier élément
# d'une colonne peut aussi être le titre de la colonne.

import stdwin
import gwin

def open(title, data): # Fonction publique permettant d'ouvrir une fenêtre
		       # contenant un tableau
	#
	# On définit les paramètres géométriques (un jour, ces paramètres
	# seront modifiables par l'utilisateur)
	#
	margin = stdwin.textwidth('  ')
	lineheight = stdwin.lineheight()
	#
	# Calculs géométriques
	#
	colstarts = [0]
	totwidth = 0
	maxrows = 0
	for coldata in data:
		# Calcul de la hauteur
		rows = len(coldata)
		if rows > maxrows: maxrows = rows
		# Calcul de la longueur
		width = colwidth(coldata) + margin
		totwidth = totwidth + width
		colstarts.append(totwidth)
	#
	# On calcule la hauteur du document et de la fênêtre
	#
	docwidth, docheight = totwidth, maxrows*lineheight
	winwidth, winheight = docwidth, docheight
	if winwidth > stdwin.textwidth('n')*100: winwidth = 0
	if winheight > stdwin.lineheight()*30: winheight = 0
	#
	# On crée la fenêtre
	#
	stdwin.setdefwinsize(winwidth, winheight)
	w = gwin.open(title)
	#
	# On définit les propriétés et on surcharge les méthodes
	#
	w.data = data
	w.margin = margin
	w.lineheight = lineheight
	w.colstarts = colstarts
	w.totwidth = totwidth
	w.maxrows = maxrows
	w.selection = (-1, -1)
	w.lastselection = (-1, -1)
	w.selshown = 0
	w.setdocsize(docwidth, docheight)
	w.draw = draw
	w.mup = mup
	w.arrow = arrow
	#
	# On renvoie la fenêtre
	#
	return w

def update(w, data): # Modifie les données
	#
	# On cache la sélection
	#
	hidesel(w, w.begindrawing())
	#
	# On obtient les anciens paramètres géométriques
	#
	margin = w.margin
	lineheight = w.lineheight
	#
	# Calculs géométriques
	#
	colstarts = [0]
	totwidth = 0
	maxrows = 0
	for coldata in data:
		# Calcul de la hauteur
		rows = len(coldata)
		if rows > maxrows: maxrows = rows
		# Calcul de la longueur
		width = colwidth(coldata) + margin
		totwidth = totwidth + width
		colstarts.append(totwidth)
	#
	# On calcule la hauteur du document et de la fênêtre
	#
	docwidth, docheight = totwidth, maxrows*lineheight
	#
	# On change les propriétés à modifier et on modifie la taille
	# de la fenêtre
	#
	w.data = data
	w.colstarts = colstarts
	w.totwidth = totwidth
	w.maxrows = maxrows
	w.change((0, 0), (10000, 10000))
	w.setdocsize(docwidth, docheight)
	w.change((0, 0), (docwidth, docheight))
	#
	# On affiche la sélection ou on oublie si elle
	# dépasse les bornes qui lui ont été allouées
	#
	showsel(w, w.begindrawing())
	if not w.selshown: w.selection = (-1, -1)

def colwidth(coldata): # Routine permettant de calculer la longueur d'une
	               # colonne
	maxwidth = 0
	for string, action in coldata:
		width = stdwin.textwidth(string)
		if width > maxwidth: maxwidth = width
	return maxwidth

def draw(w, ((left, top), (right, bottom))): # La méthode dessin
	ileft = whichcol(w, left)
	iright = whichcol(w, right-1) + 1
	if iright > len(w.data): iright = len(w.data)
	itop = divmod(top, w.lineheight)[0]
	if itop < 0: itop = 0
	ibottom, remainder = divmod(bottom, w.lineheight)
	if remainder: ibottom = ibottom + 1
	d = w.begindrawing()
	if ileft <= w.selection[0] < iright:
		if itop <= w.selection[1] < ibottom:
			hidesel(w, d)
	d.erase((left, top), (right, bottom))
	for i in range(ileft, iright):
		col = w.data[i]
		jbottom = len(col)
		if ibottom < jbottom: jbottom = ibottom
		h = w.colstarts[i]
		v = itop * w.lineheight
		for j in range(itop, jbottom):
			string, action = col[j]
			d.text((h, v), string)
			v = v + w.lineheight
	showsel(w, d)

def mup(w, detail): # La méthode souris vers le haut
	(h, v), nclicks, button, mask = detail
	icol = whichcol(w, h)
	if 0 <= icol < len(w.data):
		irow = divmod(v, w.lineheight)[0]
		col = w.data[icol]
		if 0 <= irow < len(col):
			string, action = col[irow]
			action(w, string, (icol, irow), detail)

def whichcol(w, h): # Renvoie le numéro de la colonne correspondante
	            # (ce numéro peut être >= len(w.data))
	for icol in range(0, len(w.data)):
		if h < w.colstarts[icol+1]:
			return icol
	return len(w.data)

def arrow(w, type):
	import stdwinsupport
	S = stdwinsupport
	if type = S.wc_left:
		incr = -1, 0
	elif type = S.wc_up:
		incr = 0, -1
	elif type = S.wc_right:
		incr = 1, 0
	elif type = S.wc_down:
		incr = 0, 1
	else:
		return
	icol, irow = w.lastselection
	icol = icol + incr[0]
	if icol < 0: icol = len(w.data)-1
	if icol >= len(w.data): icol = 0
	if 0 <= icol < len(w.data):
		irow = irow + incr[1]
		if irow < 0: irow = len(w.data[icol]) - 1
		if irow >= len(w.data[icol]): irow = 0
	else:
		irow = 0
	if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
		w.lastselection = icol, irow
		string, action = w.data[icol][irow]
		detail = (0, 0), 1, 1, 1
		action(w, string, (icol, irow), detail)


# Gestion de la sélection
# TODO : autoriser la sélection de multiples entrées

def select(w, selection): # Fonction publique permettant de définir l'élément sélectionné
	d = w.begindrawing()
	hidesel(w, d)
	w.selection = selection
	showsel(w, d)
	if w.selshown: lastselection = selection

def hidesel(w, d): # Cache la sélection si celle-ci s'affichait
	if w.selshown: invertsel(w, d)

def showsel(w, d): # Affiche la sélection si celle-ci était cachée
	if not w.selshown: invertsel(w, d)

def invertsel(w, d): # Inverse la sélection, si c'est une opération valide
	icol, irow = w.selection
	if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
		left = w.colstarts[icol]
		right = w.colstarts[icol+1]
		top = irow * w.lineheight
		bottom = (irow+1) * w.lineheight
		d.invert((left, top), (right, bottom))
		w.selshown = (not w.selshown)


# Démonstration

def demo_action(w, string, (icol, irow), detail): # Une action pour la démonstration
	select(w, (irow, icol))

def demo(): # Démonstration
	da = demo_action # un raccourci
	col0 = [('a1', da), ('bbb1', da), ('c1', da)]
	col1 = [('a2', da), ('bbb2', da)]
	col2 = [('a3', da), ('b3', da), ('c3', da), ('d4', da), ('d5', da)]
	col3 = []
	for i in range(1, 31): col3.append('xxx' + `i`, da)
	data = [col0, col1, col2, col3]
	w = open('tablewin.demo', data)
	gwin.mainloop()
	return w
